<html>

<head>
<title>Learning WebGL &mdash; lesson 1</title>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">

<script id="shader-fs" type="x-shader/x-fragment">
  #ifdef GL_ES
  precision mediump float;
  #endif
// FragmentProgram
//
// porting GLSL by kioku based on syoyo's AS3 Ambient Occlusion
// [http://lucille.atso-net.jp/blog/?p=638]

varying vec3 org,dir;

struct Ray
{
	vec3 org;
	vec3 dir;
};
struct Sphere
{
	vec3 center;
	float radius;
};
struct Plane
{
	vec3 p;
	vec3 n;
};

struct Intersection
{
    float t;
    vec3 p;     // hit point
    vec3 n;     // normal
    int hit;
};

void shpere_intersect(Sphere s,  Ray ray, inout Intersection isect)
{
    // rs = ray.org - sphere.center
    vec3 rs = ray.org - s.center;
    float B = dot(rs, ray.dir);
    float C = dot(rs, rs) - (s.radius * s.radius);
    float D = B * B - C;

    if (D > 0.0)
    {
		float t = -B - sqrt(D);
		if ( (t > 0.0) && (t < isect.t) )
		{
			isect.t = t;
			isect.hit = 1;

			// calculate normal.
			vec3 p = vec3(ray.org.x + ray.dir.x * t,
						  ray.org.y + ray.dir.y * t,
						  ray.org.z + ray.dir.z * t);
			vec3 n = p - s.center;
			n = normalize(n);
			isect.n = n;
			isect.p = p;
		}
	}
}

void plane_intersect(Plane pl, Ray ray, inout Intersection isect)
{
	// d = -(p . n)
	// t = -(ray.org . n + d) / (ray.dir . n)
	float d = -dot(pl.p, pl.n);
	float v = dot(ray.dir, pl.n);

	if (abs(v) < 1.0e-6)
		return; // the plane is parallel to the ray.

    float t = -(dot(ray.org, pl.n) + d) / v;

    if ( (t > 0.0) && (t < isect.t) )
    {
		isect.hit = 1;
		isect.t   = t;
		isect.n   = pl.n;

		vec3 p = vec3(ray.org.x + t * ray.dir.x,
					  ray.org.y + t * ray.dir.y,
					  ray.org.z + t * ray.dir.z);
		isect.p = p;
	}
}

Sphere sphere[3];
Plane plane;
void Intersect(Ray r, inout Intersection i)
{
	for (int c = 0; c < 3; c++)
	{
		shpere_intersect(sphere[c], r, i);
	}
	plane_intersect(plane, r, i);
}

void orthoBasis(out vec3 basis[3], vec3 n)
{
	basis[2] = vec3(n.x, n.y, n.z);
	basis[1] = vec3(0.0, 0.0, 0.0);

	if ((n.x < 0.6) && (n.x > -0.6))
		basis[1].x = 1.0;
	else if ((n.y < 0.6) && (n.y > -0.6))
		basis[1].y = 1.0;
	else if ((n.z < 0.6) && (n.z > -0.6))
		basis[1].z = 1.0;
	else
		basis[1].x = 1.0;


	basis[0] = cross(basis[1], basis[2]);
	basis[0] = normalize(basis[0]);

	basis[1] = cross(basis[2], basis[0]);
	basis[1] = normalize(basis[1]);

}

int seed = 0;
float random()
{
	seed = int(mod(float(seed)*1364.0+626.0, 509.0));
	return float(seed)/509.0;
}
vec3 computeAO(inout Intersection isect)
{
    const int ntheta = 8;
    const int nphi   = 8;
    const float eps  = 0.0001;

    // Slightly move ray org towards ray dir to avoid numerical probrem.
    vec3 p = vec3(isect.p.x + eps * isect.n.x,
                  isect.p.y + eps * isect.n.y,
                  isect.p.z + eps * isect.n.z);

    // Calculate orthogonal basis.
    vec3 basis[3];
    orthoBasis(basis, isect.n);

    float occlusion = 0.0;

    for (int j = 0; j < ntheta; j++)
    {
		for (int i = 0; i < nphi; i++)
		{
			// Pick a random ray direction with importance sampling.
			// p = cos(theta) / 3.141592
			float r = random();
			float phi = 2.0 * 3.141592 * random();

			vec3 ref;
			ref.x = cos(phi) * sqrt(1.0 - r);
			ref.y = sin(phi) * sqrt(1.0 - r);
			ref.z = sqrt(r);

			// local -> global
			vec3 rray;
			rray.x = ref.x * basis[0].x + ref.y * basis[1].x + ref.z * basis[2].x;
			rray.y = ref.x * basis[0].y + ref.y * basis[1].y + ref.z * basis[2].y;
			rray.z = ref.x * basis[0].z + ref.y * basis[1].z + ref.z * basis[2].z;

			vec3 raydir = vec3(rray.x, rray.y, rray.z);

			Ray ray;
			ray.org = p;
			ray.dir = raydir;

			Intersection occIsect;
			occIsect.hit = 0;
			occIsect.t = 1.0e+30;
			occIsect.n = occIsect.p = vec3(0, 0, 0);
			Intersect(ray, occIsect);
			if (occIsect.hit != 0)
				occlusion += 1.0;
		}
	}

	// [0.0, 1.0]
	occlusion = (float(ntheta * nphi) - occlusion) / float(ntheta * nphi);
	return vec3(occlusion, occlusion, occlusion);
}

void main()
{
	sphere[0].center = vec3(-2.0, 0.0, -3.5);
	sphere[0].radius = 0.5;
	sphere[1].center = vec3(-0.5, 0.0, -3.0);
	sphere[1].radius = 0.5;
	sphere[2].center = vec3(1.0, 0.0, -2.2);
	sphere[2].radius = 0.5;
	plane.p = vec3(0,-0.5, 0);
	plane.n = vec3(0, 1.0, 0);
	
	Intersection i;
	i.hit = 0;
	i.t = 1.0e+30;
	i.n = i.p = vec3(0, 0, 0);
		
	Ray r;
	r.org = org;
	r.dir = normalize(dir);
	seed = int(mod(dir.x * dir.y * 4525434.0, 65536.0));
	
	vec4 col = vec4(0,0,0,1);
	Intersect(r, i);
	if (i.hit != 0)
	{
		col.rgb = computeAO(i);
	}
	
	gl_FragColor = col;
}

</script>

<script id="shader-vs" type="x-shader/x-vertex">
#ifdef GL_ES
precision mediump float;
#endif
vec4 qmul(vec4 q1, vec4 q2) {
	return vec4(
		dot(q1, q2.wzyx * vec4(1.0, 1.0, -1.0, 1.0)),
		dot(q1, q2.zwxy * vec4(-1.0, 1.0, 1.0, 1.0)),
		dot(q1, q2.yxwz * vec4(1.0, -1.0, 1.0, 1.0)),
		dot(q1, q2 * vec4(-1.0, -1.0, -1.0, 1.0)));
}

vec4 conj(vec4 q) {
	return q * vec4(-1.0, -1.0, -1.0, 1.0);
}

vec4 angle_axis(float rad, vec3 axis_f3) {
	float halfRad = rad * 0.5;
	vec3 f3 = normalize(axis_f3);
	vec4 result = f3.xyzz * sin(halfRad);
	result.w = cos(halfRad);
	return result;
}

vec3 rot3(vec4 q, vec3 v3) {
	vec4 f4 = v3.xyzz * vec4(1.0, 1.0, 1.0, 0.0);
	vec4 result = qmul(qmul(q, f4), conj(q));
	return result.xyz;
}
attribute vec3 aVertexPosition;
uniform vec2 angles;
varying vec3 org,dir;
void main() {
	vec3 center = vec3(0, 0, -3.5);
	vec4 q = qmul(angle_axis(angles.x, vec3(0.0, 1.0, 0.0)), angle_axis(angles.y, vec3(1.0, 0.0, 0.0)));
	gl_Position=vec4(aVertexPosition, 1);
	org=center + rot3(q, -center);
	dir=normalize(rot3(q, -vec3(-aVertexPosition.x*1.6,-aVertexPosition.y,1)));
}
</script>


<script type="text/javascript">
/*function throwOnGLError(err, funcName, args) {
  throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to" + funcName;
};*/

  var gl;
  function initGL(canvas) {
    try {
      gl = canvas.getContext("experimental-webgl");
      gl.viewportWidth = canvas.width;
      gl.viewportHeight = canvas.height;
    } catch(e) {
    }
    if (!gl) {
      alert("Could not initialise WebGL, sorry :-(");
    }
  }


  function getShader(gl, id) {
    var shaderScript = document.getElementById(id);
    if (!shaderScript) {
      return null;
    }

    var str = "";
    var k = shaderScript.firstChild;
    while (k) {
      if (k.nodeType == 3) {
        str += k.textContent;
      }
      k = k.nextSibling;
    }

    var shader;
    if (shaderScript.type == "x-shader/x-fragment") {
      shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (shaderScript.type == "x-shader/x-vertex") {
      shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
      return null;
    }

    gl.shaderSource(shader, str);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      alert(gl.getShaderInfoLog(shader));
      return null;
    }

    return shader;
  }


  var TARGET_FPS = 20;
  var shaderProgram;
  var keyState = {up: false, down: false, left: false, right: false};
  var angles = [0, 0];
  function initShaders() {
    var fragmentShader = getShader(gl, "shader-fs");
    var vertexShader = getShader(gl, "shader-vs");

    shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
      alert("Could not initialise shaders");
    }

    gl.useProgram(shaderProgram);

    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

	shaderProgram.angles = gl.getUniformLocation(shaderProgram, "angles");
  }

  var squareVertexPositionBuffer;
  function initBuffers() {
    squareVertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
    vertices = [
         1.0,  1.0,  0.0,
        -1.0,  1.0,  0.0,
         1.0, -1.0,  0.0,
        -1.0, -1.0,  0.0
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    squareVertexPositionBuffer.itemSize = 3;
    squareVertexPositionBuffer.numItems = 4;
  }


  function drawScene() {
    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
	var DELTA = Math.PI / TARGET_FPS;
	if (keyState.up) {
	  angles[1] -= DELTA;
	}
	if (keyState.down) {
	  angles[1] += DELTA;
	}
	if (keyState.left) {
	  angles[0] -= DELTA;
	}
	if (keyState.right) {
	  angles[0] += DELTA;
	}
	gl.uniform2fv(shaderProgram.angles, angles);

    gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
  }



  function webGLStart() {
    var body = document.getElementById("body");
	body.onkeyup = onKeyUp;
	body.onkeydown = onKeyDown;
    var canvas = document.getElementById("lesson01-canvas");
    initGL(canvas);
    initShaders();
    initBuffers();

    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    gl.clearDepth(1.0);

    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);

	drawScene();
    setInterval(drawScene, 1000 / TARGET_FPS);
  }

  function onKeyDown(e) {
    switch (e.keyCode) {
	case 38: // up
	  keyState.up = true;
	  break;
	case 40: // down
	  keyState.down = true;
	  break;
	case 37: // left
	  keyState.left = true;
	  break;
	case 39: // right
	  keyState.right = true;
	  break;
	}
  }

  function onKeyUp(e) {
    switch (e.keyCode) {
	case 38: // up
	  keyState.up = false;
	  break;
	case 40: // down
	  keyState.down = false;
	  break;
	case 37: // left
	  keyState.left = false;
	  break;
	case 39: // right
	  keyState.right = false;
	  break;
	}
  }

</script>


</head>


<body id="body" onload="webGLStart();">

	<div>
		<canvas id="lesson01-canvas" style="border: none;" width="320" height="200"/>
	</div>

  <div><ul>
	  <li>use "array keys" to rotate</li>
	  <li>original webgl sample is <a href="http://learningwebgl.com/blog/?p=28">here</a>.</li>
	  <li>original ao shader is <a href="http://kioku.sys-k.net/archives/2009/01/gpuaoglsl.html">here</a>.</li>
  </ul></div>
</body>

</html>
